package density;

import density.model.Cluster;
import density.model.DataPoint;
import density.utils.DBSCAN;
import density.vo.MaxDensity;

import java.io.*;
import java.util.LinkedList;
import java.util.List;

/**
 * Created by jiangyangbo on 2017/9/21.
 * Modified by liuyu on 2020/05/09.
 */
public class DensityHelper {
    private static double eps;
    /**
     * 按百度坐标的距离计算，1米距离的计算值
     */
    private static final double eps_bd09 = 0.000000000098;
    /**
     * WGS84坐标：经度距离1米约为0.0000089，纬度距离1米约为0.0000094。取其中间值0.0000092
     * Created by liuyu on 2020/05/09
     */
    private static final double eps_wgs84 = 0.0000092;

    /**
     * 对坐标集进行密度分析，得出eps范围内的最大密度
     * @param datas List of DataPoint
     * @return 最大密度 数值 及其所在的矩形区域坐标
     */
    public static MaxDensity doDensityAnalysis(List<DataPoint> datas, String coordType) {
        initEps(coordType);
        DBSCAN scan = new DBSCAN(1, eps, datas);
        //密度分析得到所有簇集
        List<Cluster> clusters = scan.dBScan();
        int max = 0;
        //遍历所有簇集，获取最大簇集
        Cluster maxCluster = null;
        for (Cluster cluster : clusters) {
            max = Math.max(max, cluster.getMembers().size());
            if (cluster.getMembers().size() == max) {
                maxCluster = cluster;
            }
        }
        MaxDensity md = null;
        if(maxCluster != null){
            //计算最大簇集的最大矩形密度
            md = getMax(maxCluster, eps);
            //遍历其他簇集，簇集集合比最大密度大的，再计算其矩形密度来比对
            for (Cluster cluster : clusters) {
                if (cluster.getMembers().size() > md.getDensity() && !cluster.equals(maxCluster)) {
                    MaxDensity tmp = getMax(cluster, eps);
                    //小簇集矩形密度大于之前的最大密度，则替换
                    if (tmp.getDensity() > md.getDensity()) {
                        md = tmp;
                    }
                }
            }
        }
        return md;
    }

    /**
     * 获取簇集内的最大矩形密度
     * @param cluster
     * @param eps
     * @return
     */
    private static MaxDensity getMax(Cluster cluster, double eps) {
        //获取簇集左上角坐标 和 右下角坐标
        double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE, maxX = Double.MIN_VALUE, maxY = Double.MIN_VALUE;
        for (DataPoint dp : cluster.getMembers()) {
            minX = Math.min(minX, dp.getX());
            minY = Math.min(minY, dp.getY());
            maxX = Math.max(maxX, dp.getX());
            maxY = Math.max(maxY, dp.getY());
        }

        MaxDensity md = new MaxDensity();
        double step = eps / 20; //以10米为步长开始遍历
        for (double x = minX; x < maxX; x += step) {
            for (double y = minY; y < maxY; y += step) {
                int density = 0;
                for (DataPoint dp : cluster.getMembers()) {
                    if (dp.getX() >= x && dp.getX() <= x + eps && dp.getY() >= y && dp.getY() <= y + eps) {
                        density++;
                    }
                }
                if (density > md.getDensity()) {
                    md.setDensity(density);
                    md.setPosLeft(x);
                    md.setPosTop(y);
                    md.setPosRight(x + eps);
                    md.setPosDown(y + eps);
                }
            }
        }
        return md;
    }

    /**
     * @param args
     */
    public static void main(String[] args) throws IOException {        
        /*for (int i = 1; i<=5; i++) {
        	List<DataPoint> datas = init("d:/temp/xy12/xy" + i + ".txt");
            MaxDensity max = doDensityAnalysis(datas);
            
            System.out.println("xy " + i + " maxDensity:" + max.getDensity() + " posx:" + max.getPosLeft() + " posY:" + max.getPosTop() );            
        }*/

        List<DataPoint> datas = init("d:/temp.txt");
        System.out.println(String.format("Point1:%s, Point2:%s, 距离：%s米",
                datas.get(0).toString(), datas.get(1).toString(),
                getDistance(datas.get(0), datas.get(1), "BD09")));
    }

    /**
     *
     * @param dp1
     * @param dp2
     * @return
     */
    public static double getDistance(DataPoint dp1, DataPoint dp2, String coordType){
        initEps(coordType);

        double distance=0.0;
        distance += Math.pow((dp1.getX()-dp2.getX()), 2);
        distance += Math.pow((dp1.getY()-dp2.getY()), 2);
        distance = Math.pow(distance, 0.5) / eps;
        return distance;
    }

    /**
     * 读取数据样本文件生成数据集
     * @param dataPath
     * @return
     * @throws IOException
     */
    private static List<DataPoint> init(String dataPath) throws IOException {
        if(dataPath==null)
            throw new RuntimeException("缺少测试数据文件！");
        File file = new File(dataPath);
        if (!file.exists()) {
            throw new RuntimeException("测试数据文件不存在！");
        }
        List<DataPoint> dataSet = new LinkedList<DataPoint>();
        InputStreamReader reader = new InputStreamReader(new FileInputStream(file), "utf-8");
        BufferedReader bufferedReader = new BufferedReader(reader);
        String row = bufferedReader.readLine();
        while (row!=null) {
            String[] rdata = row.split(",");
            dataSet.add(new DataPoint(Double.parseDouble(rdata[0].trim()), Double.parseDouble(rdata[1].trim())));
            row = bufferedReader.readLine();
        }
        return dataSet;
    }

    /**
     * 经纬度校验只校验正数 0-90.000000 0-180.000000 范围内
     * 经度longitude: (?:[0-9]|[1-9][0-9]|1[0-7][0-9]|180)\\.([0-9]{6})
     * 纬度latitude：  (?:[0-9]|[1-8][0-9]|90)\\.([0-9]{6})
     * @return
     */
    public static boolean checkItude(String longitude,String latitude) {
        String reglo = "((?:[0-9]|[1-9][0-9]|1[0-7][0-9])\\.([0-9]{0,6}))|((?:180)\\.([0]{0,6}))";
        String regla = "((?:[0-9]|[1-8][0-9])\\.([0-9]{0,6}))|((?:90)\\.([0]{0,6}))";
        longitude = longitude.trim().format("%.6f",Double.valueOf(longitude));
        latitude = latitude.trim().format("%.6f",Double.valueOf(latitude));
        return longitude.matches(reglo)==true?latitude.matches(regla):false;
    }

    /**
     * 初始化距离值
     * @param coordType
     */
    private static void initEps(String coordType) {
        switch (coordType) {
            case "BD09":
                eps = eps_bd09;
                break;
            case "WGS84":
            default:
                eps = eps_wgs84;
                break;
        }
    }
}